package base.util;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.TrustStrategy;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.SSLContext;
import java.nio.charset.StandardCharsets;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 自行实现的一个类似与Postman或者jmeter的工具
 */
public class HttpClientUtil {
    private static final HttpClientBuilder httpClientBuilder = HttpClients.custom();

    static {
        /**
         * 1、绕过不安全的 https 请求的证书验证
         */
        final Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.INSTANCE)
                .register("https", trustHttpsCertificates())
                .build();

        /**
         * 2、创建连接池
         */
        // 创建 ConnectionManager
        final PoolingHttpClientConnectionManager pool = new PoolingHttpClientConnectionManager(registry);
        // 连接池最大有50个连接
        pool.setMaxTotal(50);
        /**
         * 一个域名就是一个路由，每个路由默认从池中分配多少个连接去访问（路由=IP+Port唯一确定一个路由），
         * 也就是设置每个主机的最大连接数，防止爬取多个站点数据时，连接池连接在一个站点上，只对某个站点集中采集，而导致其它站点在等候，
         * 达到均匀分配连接池连接同时采集多个站点任务。
         * 简单的说就是：允许连接池中在这里设置的10个连接去访问某个站点，另外10个连接去访问其它站点，直到连接池连接被消耗完为止。
          */
        pool.setDefaultMaxPerRoute(10);

        System.out.println("连接池最大连接数："+pool.getMaxTotal());
        System.out.println("每一个路由的最大连接数："+pool.getDefaultMaxPerRoute());
        System.out.println("连接池的最大连接数："+pool.getTotalStats().getMax());
        System.out.println("连接池里面有多少连接被占用："+pool.getTotalStats().getLeased());
        System.out.println("连接池里面多少连接可用："+pool.getTotalStats().getAvailable());
        httpClientBuilder.setConnectionManager(pool);

        /**
         * 3、设置请求默认配置（应用服务器配置和网络各不同，防止我们程序一直等待）
         */
        final RequestConfig config = RequestConfig.custom()
                //允许 TCP 3次握手时长
                .setConnectTimeout(5000)
                // 等待服务器响应的时长(响应数据传输时长)
                .setSocketTimeout(5000)
                // 从连接池获取连接的等待时长
                .setConnectionRequestTimeout(5000)
                .build();
        httpClientBuilder.setDefaultRequestConfig(config);

        /**
         * 4、设置默认的一些 header
         */
        final ArrayList<Header> defaultHeaders = new ArrayList<>();
        final BasicHeader userAgent = new BasicHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36");
        defaultHeaders.add(userAgent);
        httpClientBuilder.setDefaultHeaders(defaultHeaders);
    }

    /**
     * 构造安全连接工厂
     */
    private static ConnectionSocketFactory trustHttpsCertificates() {
        SSLContextBuilder sslContextBuilder = new SSLContextBuilder();
        try {
            sslContextBuilder.loadTrustMaterial(null, new TrustStrategy() {
                // 判断是否信任url
                @Override
                public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                    return true;
                }
            });
            SSLContext sslContext = sslContextBuilder.build();
            return new SSLConnectionSocketFactory(sslContext, new String[]{"SSLv2Hello","SSLv3","TLSv1","TLSv1.1","TLSv1.2"},
                    null, NoopHostnameVerifier.INSTANCE);
        } catch (Exception e) {
            System.out.println("构造安全连接工厂失败");
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 发送 Get 请求
     * @param url
     * @param headers
     * @return
     */
    public static String doGet(String url, Map<String, String> headers) {
        final CloseableHttpClient httpClient = httpClientBuilder.build();
        try {
            HttpGet httpGet = new HttpGet(url);
            if (headers != null) {
                headers.forEach((k,v)->{
                    httpGet.setHeader(k,v);
                });
            }
            String responseBody = httpClient.execute(httpGet, httpResponse -> {
                int status = httpResponse.getStatusLine().getStatusCode();
                if (status < 200 || status >= 300) {
                    // ... handle unsuccessful request
                }
                HttpEntity entity = httpResponse.getEntity();
                return entity != null ? EntityUtils.toString(entity, StandardCharsets.UTF_8) : null;
            });
            // ... do something with response
            return responseBody;
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 表单 post 提交
     * @param url
     * @param headers
     * @param list 表单字段值
     * @return
     */
    public static String doPostForm(String url, Map<String, String> headers, List<NameValuePair> list) {
        final CloseableHttpClient httpClient = httpClientBuilder.build();
        try {
            HttpPost httpPost = new HttpPost(url);
            if (headers != null) {
                headers.forEach((k,v)->{
                    httpPost.setHeader(k,v);
                });
            }
            httpPost.setHeader("Content-Type","application/x-www-form-urlencoded; charset=utf-8;");
            final UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(list, StandardCharsets.UTF_8);
            httpPost.setEntity(formEntity);

            String responseBody = httpClient.execute(httpPost, httpResponse -> {
                int status = httpResponse.getStatusLine().getStatusCode();
                if (status < 200 || status >= 300) {
                    // ... handle unsuccessful request
                }
                HttpEntity entity = httpResponse.getEntity();
                return entity != null ? EntityUtils.toString(entity, StandardCharsets.UTF_8) : null;
            });
            // ... do something with response

        } catch (Throwable e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 发送 JSON 数据请求
     * @param url
     * @param body
     * @param headers
     * @return
     */
    public static String postJson(String url, String body, Map<String, String> headers){
        final CloseableHttpClient httpClient = httpClientBuilder.build();
        try {
            HttpPost httpPost = new HttpPost(url);
            if (headers != null) {
                headers.forEach((k,v)->{
                    httpPost.setHeader(k,v);
                });
            }
            // 确保请求头是 json 类型，如果headers提供了该值，这里会覆盖headers提供的
            httpPost.setHeader("Content-Type","application/json; charset=utf-8");
            final StringEntity jsonEntity = new StringEntity(body, ContentType.APPLICATION_JSON);
            jsonEntity.setContentType("application/json; charset=utf-8");
            jsonEntity.setContentEncoding(StandardCharsets.UTF_8.name());

            httpPost.setEntity(jsonEntity);
            String responseBody = httpClient.execute(httpPost, httpResponse -> {
                int status = httpResponse.getStatusLine().getStatusCode();
                if (status < 200 || status >= 300) {
                    // ... handle unsuccessful request
                }
                HttpEntity entity = httpResponse.getEntity();
                return entity != null ? EntityUtils.toString(entity, StandardCharsets.UTF_8) : null;
            });
            // ... do something with response
            
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return null;
    }





}
